<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Rest extends CI_Controller {
	function __construct() {
		error_reporting(0);	// force no error biar format output gak broken
		parent::__construct();

		$this->target_domain = "";

		$this->svn_version = "";
		try {
			$fgc = file_get_contents(dirname(__FILE__) . '/.svn/entries');
			if (preg_match("/dir\n(.+)/", $fgc, $matches) )
				$this->svn_version = intval($matches[1]);
		} catch (Exception $e) {}

		$this->config->load("rest");
		$this->access_keys = $this->config->item('access_keys');
		if (!$this->access_keys) $this->access_keys = array();

		$this->powered_by = $this->config->item('powered_by');
		if (!$this->powered_by) $this->powered_by = "sx_api";

		$this->access_log = $this->config->item('access_log');
		if (!$this->access_log) $this->access_log = "/tmp/api_access.log";
	}

	// landing page and listing
	function index(){

		$api = $this->listLib();
		$konten = array(
			  "related_docs" => true, "resource_info" => true, "dropdown" => true
			, "api_list"	=> $api["list"]
			, "api_counts"	=> count( $api["list"] )
			, "svn_version"	=> $api["version"]
		);

		$this->draw("rest/listing", $konten);
	}

	// api action docs
	function docs($method, $api, $action){
		$this->load->helper("output");
		if (!$method || !$api || !$action) redirect(base_url() . "rest/");

		$lib = $this->loadLib( array("api_name" => $api) );
		$action_info = $lib->info["methods"][strtoupper($method)][$action];

		if (!$lib || !$action_info) $this->pages("404");

		$theapis = $this->listLib();

		$command = $this->render_curl($method, $api, $action, $action_info);
		$data = array(   "page_title" => strtoupper($method) . " $api/$action"
				, "breadcrumbs" => array( array("uri" => base_url() . "rest/docs/#$api", "label" => $lib->info["name"]) )
				, "related_docs" => true, "resource_info" => true, "dropdown" => true
				, "method" => $method, "api" => $api, "action" => $action
				, "info" => $action_info
				, "name" => $lib->info["name"]
				, "last_update" => $lib->info["timestamp"]
				, "actions" => $lib->info["actions"]
				, "api_list" => $theapis["list"]
				, "command" => $command
			);

		$this->draw("rest/docs", $data );
	}

	// handle static pages
	function pages($name){
		$statics = array(
			"404" => "What are you looking for?",
			"access_key" => "Access Key Authentication",
			"responses" => "Responses Format",
			"rate_limit" => "Rate Limit"
		);

		if (isset($statics[$name])) {
			$theapis = $this->listLib();
			$konten = array( "page_title" => $statics[$name]
					, "api_list" => $theapis["list"]
					, "breadcrumbs" => array( array('uri' => base_url() . "rest/docs/$name", "label" => $statics[$name]) )
					, "dropdown" => true
					, "resource_information" => false
					, "related_docs" => false 
			);
			$this->draw("rest/pages/$name", $konten); exit;
		} else $this->pages("404");
	}

	function render_output_sample($method, $api, $action, $info) {
		/* not yet implemented */
	}

	function render_curl($method, $api, $action, $info) {
		$tmp_params = array();
		foreach ($info["params"] as $name => $param) {
			if (isset($param["example"]))
				$tmp_params[$name] = $param["example"];
		}
		$params = http_build_query($tmp_params);

		$cmd = 'curl';
		if (!$info['public']) $cmd .= ' --header "ACCESS_KEY:YOUR_ACCESS_KEY"';

		$uri = base_url() . "rest/$api/$action.json";

		switch (strtoupper($method)) {
			case "GET":
				$cmd .= " \"$uri?" . $params . "\"";
			break;
			case "POST":
				$cmd .= " -d \"$params\" \"$uri\"";
			break;
			case "DELETE":
				$cmd .= " -d \"$params\" -X DELETE \"$uri\"";
			break;
			case "PUT":
				$cmd .= " -d \"$params\" -X PUT \"$uri\"";
			break;
		}
		return $cmd;
	}

	// execute api
	function apiexec($cliarg=null){
		$this->apiStart = microtime(true);

		header("X-Powered-by: {$this->powered_by}");

		$met = $this->method = strtoupper($_SERVER["REQUEST_METHOD"]);
		$arg = $this->parse_request($_REQUEST);

		$apiname = $arg["apiname"];
		$apichild = explode("/",$apiname);
		if (count($apichild) > 1) {
			$apiname = $apichild[0];
			array_shift($apichild);
			$action = implode("_", $apichild);
			$met = $met . "_" . $action;
		} else $action = $apichild;

		$lib = $this->loadLib( array("api_name" => $apiname) );

		// check if the API exists or not corrupted
		if( !$lib ){
			$this->output->set_status_header(404);
			die("API " . $arg["apiname"] . " not found");
		}


		if (!method_exists($lib,"$met")) {
			$this->output->set_status_header(405);
			die("Action `$action` with {$this->method} METHOD not found");
		}

		$lib->method = $this->method;

		// listing api dan documentation boleh open public, tapi eksekusi mesti pake auth ;)
		if (!$lib->info["methods"][$this->method][$action]["public"]) $this->request_auth();

		if ( $this->_is_rate_limited() ) {
			$this->output->set_status_header(403);
			die("Rate Limited");
		}

		// fetching received parameters
		$this->check_request_params($this->method);

		// preparing default vars for working api
		$this->prepare_api_out($lib);

		// checking incompletes parameters
		$incompletes = $this->get_incomplete_fields($lib->info["methods"][$this->method][$action]["params"], $this->request_params);
		if (count($incompletes) > 0) {
			$this->output->set_status_header(412);
			die("Incomplete fields: " . implode(",", $incompletes) );
		}

		// after all step, now we proceed the request
		$lib->$met();

		$ret = array( "status" => $lib->status, "api_ver" => $this->svn_version, "response" => $lib->response);
		if ($lib->error) $ret["error"] = $lib->error;

		$this->_log_my_access(date("[H:i:s D/M/Y]", time()) . "\t" . $this->method . "\t" . $arg["apiname"]);
		$ret = $this->api_render($ret, $arg["format"]);
		echo $ret;
	}


	function api_render( $ret, $output="json" ){

		$this->load->helper('output');
		$this->apiEnd = microtime(true);
		$response = $ret["response"];
		unset($ret["response"]);

		if (!isset($ret["status"])) $ret["status"] = 200;
		$this->output->set_status_header($ret["status"]);

		// check kalo2 hasil dari apinya punya status gagal atau error
		if (in_array($ret["status"], array(403,404,400) )) {
			$message = isset($ret["error"]) ? $ret["error"] : "UNKNOWN ERROR";
			die($message);
		}

		$ret["elapsed_time"] = submicrotime(($this->apiEnd - $this->apiStart), 6) . " s";
		$ret["response"] = $response;


		switch ($output) {
			case "xml":
				header("content-type: text/xml");
				$out = array_to_xml($ret);
				break;
			case "serialize": case "php":
				$out = serialize($ret);
				break;
			case "json":
				header("content-type: application/json");
				$out = json_encode($ret);
				$out = (isset($_REQUEST['callback']) && !empty($_REQUEST['callback'])) ? $_REQUEST['callback'] . '(' . $out . ');' : $out;
				break;
			//case "csv":
			//	echo $out = csv_show_array($ret);
			//	break;
			case "html": default:
				$out = html_show_array($ret);
				break;
		}
		$out = preg_replace('/[^(\x20-\x7F)]*/', '', $out);
		return $out;
	}

	function _is_rate_limited() {
		if (!isset($this->max_limit)) return false;

		$ip = $_SERVER["REMOTE_ADDR"];
		if (file_exists($this->access_log)) {
			$isi = file_get_contents($this->access_log);
			$cek = explode($ip, $isi);
			$left = intval($this->max_limit - count($cek));
			header("X-Rate-Limit-Left: $left");
			if ($left < 1) return true;
			else return false;
		};
	}

	function daily_move_log() {
		/* dont forget add to cron job @daily */
		/* base_url() rest/cleaning_services */

		$old = $this->access_log . "_" . date("j_F_Y_", time()) . "_" . time();
		system("mv {$this->access_log} $old");
	}

	function _log_my_access($teks) {
		$ip = $_SERVER["REMOTE_ADDR"];
		$fp = fopen($this->access_log, "a");
		fwrite($fp, $ip . "\t" . $teks . "\n");
		fclose($fp);
	}

	function request_auth() {
		$auth = isset($_SERVER["HTTP_ACCESS_KEY"]) ? trim($_SERVER["HTTP_ACCESS_KEY"]) : null;
		if (!$auth || in_array(strtolower($auth), $this->access_keys) ) {
			$this->output->set_status_header(401);
			die("Not Authorized. Unknown Access Key.");

		} else {
			$this->max_limit = intval($this->access_keys[$auth]['limit']);
			header("X-Provided-To: {$this->access_keys[$auth]['name']}");
		}
	}

	function get_incomplete_fields($target_fields, $current_fields) {
		$miss = array();
		foreach ($target_fields as $key=>$value) {
			if ($value["required"]) {
				if (!isset($current_fields[$key])) $miss[] = $key;
			}
		}
		return $miss;
	}

	function prepare_api_out(&$lib) {
		$lib->params = $this->request_params;
		$lib->response = array();
		$lib->error = "UNKNOWN ERROR";
		$lib->status = 500;
	}

	function check_request_params($method) {
		$this->request_params = array();

		switch (strval($method)) {
			case 'DELETE': case 'PUT':
				parse_str( file_get_contents( 'php://input' ), $this->request_params );
		        	break;

			case 'GET':
				$this->request_params = $_GET;
				break;
			case 'POST':
				$this->request_params = $_POST;
				break;
		}
	}

	// @arg supplied from $_REQUEST as well
	// return splited apiname; params
	function parse_request($arg){
		$ret = array(
			 "apiname"  => ""
			,"format" 	=> ""
			,"query" 	=> array()
		);
		$param = $arg;
		if(isset($param["r"]) && preg_match("/rest\/([^\.]+).(json|xml|php|html|csv)/", $param["r"], $cucok)){
			$ret["apiname"] = $cucok[1];
			$ret["format"] 	= $cucok[2];
		}
		unset($param["r"]);

		foreach($param as $field => $val)
			$ret["query"][$field] = $val;

		return $ret;
	}

	function get_apidir($apis=""){
		$path = realpath("./") . "/web/libraries/" . $apis . "/";
		return (is_dir($path) ? $path : false);
	}


	function listLib() {

		$apis_dir = $this->get_apidir("apis");
		if(!$apis_dir) return false;

		$infoLib = array();
		if( $hDir = opendir( $apis_dir ) ){
			while($entryName = readdir($hDir) ) {
				if (strpos($entryName,".php") && !strpos($entryName,".swp") && $entryName != "index.php") {

					$entryName = str_replace(".php","",$entryName);
					$info["resource"] = $entryName;
					$lib = $this->loadLib(array("api_name" => $entryName));

					$target = $apis_dir . $entryName . ".php";
					$info["timestamp"] = filemtime($target);
					$info["signature"] = md5(file_get_contents($target));
					$info["info"] = $lib->info;
					array_push($infoLib, $info);
				}
			}
		}
		closedir($hDir);
		return array("list" => $infoLib, "version" => $this->svn_version);
	}


	function submicrotime($t="0.0", $teliti=8 ){
		$t = (string)$t;
		return (float)substr($t, 0, ($teliti + stripos($t, ".") + 1) );
	}

	function loadLib( $api_arg ) {

		$act = strtolower($api_arg["api_name"]);
		$fileLib = $this->get_apidir("apis")."$act.php";
		$incLib = "apis/$act.php";
		$ret = false;
		if (file_exists($fileLib)) {
  			$this->load->library( $incLib );
			if( class_exists( $act ) && isset($this->$act) ){
				$this->$act->params = array();

				$actions = array();
				$methods = $this->$act->info["methods"];
				foreach ($methods as $met => $met_list) {
					foreach ($met_list as $label => $acts) {
						$actions[] = array($met, $label);
					}
				}

				$this->$act->info["actions"] = $actions;

				$this->$act->info["timestamp"] = filemtime($fileLib);
				$ret = $this->$act;
			}else{
				$ret = "Corrupted API : ".$act;
			}
		}
		return $ret;
	}



	function draw($layout, $konten=array(), $sidebar=""){

		// elemtn rendering
		$elemen = array();

		if( is_array($konten) && count($konten) > 0 )
			$elemen["konten"] = $this->_show($layout, $konten);
		$elemen["menu"] = $this->_show("rest/menu");
		$out = $this->_show("rest/template", $elemen);
		echo $out;
	}


	function _show($file,$konten=array()) {
		$CI = & get_instance();
		return $CI->load->view($file,$konten,true);
	}

}

/* End of file welcome.php */
/* Location: ./application/controllers/welcome.php */

